package be.kuleuven.cs.mop.domain.model.impl;


import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import be.kuleuven.cs.mop.domain.exceptions.TaskManagerException;
import be.kuleuven.cs.mop.domain.model.Project;
import be.kuleuven.cs.mop.domain.model.Task;



/**
 * Domain class implementation of a project
 * Implements the {@link Project} interface
 */
public class ProjectImpl extends WorldElement implements Project {
	
	/**
	 * A project has a name and a set of tasks
	 */
	private final String		description;
	private final Set<TaskImpl>	tasks		= new HashSet<TaskImpl>();
	
	
	/**
	 * Creates a new {@link ProjectImpl}
	 * 
	 * @param world 		- The <code>World</code>
	 * @param description 	- The name for this new {@link ProjectImpl} 
	 * 
	 * @throws TaskManagerException	PROJECT_INVALID_DESCRIPTION - !{@link ProjectImpl#isValidDescription(String)}
	 */
	public ProjectImpl(final World world, final String description) throws TaskManagerException {
		super(world);
		
		if (!isValidDescription(description))
			throw new TaskManagerException(TaskManagerException.PROJECT_INVALID_DESCRIPTION);
		
		this.description = description;
		
		getWorld().add(this);
	}
	
	
	/**
	 * Assigns the specified {@link Task} to this {@link Project}
	 * If the task is already part of this project, nothing happens
	 * If the task was already assigned to some other project, it won't be anymore after this method executed
	 * @param task - The <code>Task</code> to be assigned to this {@link Project}
	 * 
	 * @throws TaskManagerException INVALID_TASK - !{@link #isValidTask(TaskImpl)}
	 */
	public void assignTask(final TaskImpl task) throws TaskManagerException {
		
		if (!isValidTask(task))
			throw new TaskManagerException(TaskManagerException.INVALID_TASK);
		
		if (!getTasks().contains(task)) {
			try {
				task.getProject().tasks.remove(task);
			} catch (final TaskManagerException e) {
				//	ignored
			}
			task.setProject(this);
			tasks.add(task);
		}
	}
	
	/**
	 * Returns the name of this project
	 */
	public String getDescription() { return description; }
	
	/**
	 * Returns an unmodifiable set of tasks this project contains
	 */
	public Set<TaskImpl> getTasks() { return Collections.unmodifiableSet(tasks); }
	
	/**
	 * Validates the given description
	 * @param descr	- The given description
	 * @return false if descr is null, empty or already in use by an other project
	 */
	private boolean isValidDescription(final String descr) {
		return ((descr != null) && !descr.isEmpty() && !projectAlreadyExists(descr));
	}
	
	/**
	 * Checks whether a given project name is already in use by an earlier created project
	 * @param name - name of project
	 * @return false if name is already in use by an earlier created project
	 */
	private boolean projectAlreadyExists(String name) {
		for(ProjectImpl project : getWorld().getProjects()) {
			if(project.getDescription().equals(name))
				return true;
		}
		return false;
	}

	/**
	 * Validates the given task
	 * @param task	- The given task
	 * @return false if task is null or not present in the system
	 */
	private boolean isValidTask(final TaskImpl task) {
		return (task != null && getWorld().isPresent(task));
	}
	
	/**
	 * Removes this project
	 * Also removes all tasks this project contained, non-recursively and removes dependency links to these tasks
	 */
	public void remove() {
		for (final TaskImpl task : getTasks())
			task.remove();
		getWorld().remove(this);
	}
	
	/**
	 * Returns a string representation of this project
	 */
	@Override
	public String toString() {
		return new StringBuilder(10 + description.length())
		.append("Project '").append(description).append("'")
		.toString();
	}
}